﻿
/*============== simpleIRC - http://matthias.van-eeghem.com/ ==============*
 *                                                                         *
 *  File: Server.cs                                                        *
 *  Project: IRC.dll                                                       *
 *  Author(s): Matthias Van Eeghem <matthias@van-eeghem.com>               *
 *                                                                         *
 * ========================================================================*
 *                                                                         *
 *  This program is free software: you can redistribute it and/or modify   *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation, either version 3 of the License, or      *
 *  (at your option) any later version.                                    *
 *                                                                         *
 *  This program is distributed in the hope that it will be useful,        *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 *  GNU General Public License for more details.                           *
 *                                                                         *
 *  You should have received a copy of the GNU General Public License      *
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
 *                                                                         *
===========================================================================*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Threading;

namespace simpleIRC
{
    // All the defines of the delegates come here.
    public delegate void OnClientTimeout(int serverIndex);
    public delegate void OnMessage(string channelName, int serverIndex, string userName, string message);
    public delegate void OnJoin(string channelName, int serverIndex, string userName);
    public delegate void OnPart(string channelName, int serverIndex, string userName, string highestLevelBeforePart, string reason);
    public delegate void OnQuit(string channelName, int serverIndex, string nickName, string userName, string host, string highestLevelBeforeQuitting, string reason);
    public delegate void OnEmptyTopic(string channelName, int serverIndex);
    public delegate void OnFoundTopic(string channelName, int serverIndex, string topic);
    public delegate void OnTopicSetBy(string channelName, int serverIndex, string name, int timeSet);
    public delegate void OnNamesReceived(string channelName, int serverIndex, string[] names);
    public delegate void OnTopicChange(string channelName, int serverIndex, string userName, string newTopic);
    public delegate void OnNickChange(string channelName, int serverIndex, string userName, string newNick);
    public delegate void OnChannelNotice(string sender, string channelName, int serverIndex, string message);
    public delegate void OnPrivateNotice(string sender, int serverIndex, string message);
    public delegate void OnPrivateMessage(string sender, int serverIndex, string message);
    public delegate void OnPrivateActionSent(string sender, int serverIndex, string action);
    public delegate void OnModeChange(string channelName, int serverIndex, string nickName, string mode, string[] names, string[] highestLevelBeforeModeChange);
    public delegate void OnActionSent(string channelName, int serverIndex, string userName, string action);
    public delegate void OnKick(string channelName, int serverIndex, string kickerName, string kickeeName, string reason, string highestLevelBeforeKick);
    public delegate void OnInvalidPassword(string channelName, int serverIndex, string message);
    public delegate void OnChannelForward(string channelName, string forward, int serverIndex, string message);
    public delegate void OnCannotJoin(string channelName, int serverIndex, string message);
    public delegate void OnMotdReceive(int serverIndex, string motdMessage);
    public delegate void OnMotdStart(int serverIndex, string message);
    public delegate void OnMotdEnd(int serverIndex, string message);
    public delegate void OnConnectionInitialised(int serverIndex);
    public delegate void OnConnectionFail(int serverIndex, int errorCode);
    public delegate void OnServerInformationReceived(int serverIndex, string message);
    public delegate void OnCommandsSupportedReceived(int serverIndex, string commandsSupported);
    public delegate void OnDisplayedHostReceived(int serverIndex, string hostname);
    public delegate void OnPingReplyReceived(int serverIndex, string nickName, string ping);
    public delegate void OnTimeReplyReceived(int serverIndex, string nickName, string time);
    public delegate void OnVersionReplyReceived(int serverIndex, string nickName, string version);
    public delegate void OnNoSuchNickOrChannel(int serverIndex, string target, string message);
    public delegate void OnNoSuchChannel(int serverIndex, string channelName, string message);
    public delegate void OnNameAlreadyInUse(int serverIndex, string nickName, string message);
    public delegate void OnRegisteredNickReceived(int serverIndex, string nickName, string message);
    public delegate void OnIsAvailableForHelpReceived(int serverIndex, string nickName, string message);
    public delegate void OnWhoisInfoReceived(int serverIndex, string nickName, string name, string hostname, string userName);
    public delegate void OnUserConnectedFromReceived(int serverIndex, string nickName, string server, string info);
    public delegate void OnUserIsOperatorReceived(int serverIndex, string nickName, string message);
    public delegate void OnUserIsIdlingReceived(int serverIndex, string nickName, int idleTime, int signOnTime);
    public delegate void OnEndOfWhoisReceived(int serverIndex, string nickName, string message);
    public delegate void OnUsersOnChannelsReceived(int serverIndex, string nickName, string channelList);
    public delegate void OnUserIsLoggedInAsReceived(int serverIndex, string nickName, string name, string message);
    public delegate void OnUserConnectingFromReceived(int serverIndex, string nickName, string connectingFrom);
    public delegate void OnUserIsUsingModesReceived(int serverIndex, string nickName, string modes);
    public delegate void OnUnknownCommandReceived(int serverIndex, string command, string message);
    public delegate void OnInviteCommandReceived(int serverIndex, string nickName, string invitedTo);
    public delegate void OnChannelUrlReceived(int serverIndex, string channeName, string url);

    /// <summary>
    /// This class contains all the Server related core functions.
    /// </summary>
    
    public class Server
    {
        // Define all events here:
        public static event OnClientTimeout OnClientTimeoutEvent;
        public static event OnMessage OnMessageEvent;
        public static event OnJoin OnJoinEvent;
        public static event OnPart OnPartEvent;
        public static event OnQuit OnQuitEvent;
        public static event OnEmptyTopic OnEmptyTopicEvent;
        public static event OnFoundTopic OnFoundTopicEvent;
        public static event OnTopicSetBy OnTopicSetByEvent;
        public static event OnNamesReceived OnNamesReceivedEvent;
        public static event OnTopicChange OnTopicChangeEvent;
        public static event OnNickChange OnNickChangeEvent;
        public static event OnChannelNotice OnChannelNoticeEvent;
        public static event OnPrivateNotice OnPrivateNoticeEvent;
        public static event OnPrivateMessage OnPrivateMessageEvent;
        public static event OnPrivateActionSent OnPrivateActionEvent;
        public static event OnModeChange OnModeChangeEvent;
        public static event OnActionSent OnActionEvent;
        public static event OnKick OnKickEvent;
        public static event OnInvalidPassword OnInvalidPasswordEvent;
        public static event OnChannelForward OnChannelForwardEvent;
        public static event OnCannotJoin OnCannotJoinEvent;
        public static event OnMotdReceive OnMotdReceiveEvent;
        public static event OnMotdStart OnMotdStartEvent;
        public static event OnMotdEnd OnMotdEndEvent;
        public static event OnServerInformationReceived OnServerInformationReceivedEvent;
        public static event OnCommandsSupportedReceived OnCommandsSupportedReceivedEvent;
        public static event OnConnectionInitialised OnConnectionInitialisedEvent;
        public static event OnConnectionFail OnConnectionFailEvent;
        public static event OnDisplayedHostReceived OnDisplayedHostReceivedEvent;
        public static event OnPingReplyReceived OnPingReplyReceivedEvent;
        public static event OnTimeReplyReceived OnTimeReplyReceivedEvent;
        public static event OnVersionReplyReceived OnVersionReplyReceivedEvent;
        public static event OnNoSuchNickOrChannel OnNoSuchNickOrChannelEvent;
        public static event OnNoSuchChannel OnNoSuchChannelEvent;
        public static event OnNameAlreadyInUse OnNameAlreadyInUseEvent;
        public static event OnRegisteredNickReceived OnRegisteredNickReceivedEvent;
        public static event OnIsAvailableForHelpReceived OnIsAvailableForHelpReceivedEvent;
        public static event OnWhoisInfoReceived OnWhoisInfoReceivedEvent;
        public static event OnUserConnectedFromReceived OnUserConnectedFromReceivedEvent;
        public static event OnUserIsOperatorReceived OnUserIsOperatorReceivedEvent;
        public static event OnUserIsIdlingReceived OnUserIsIdlingReceivedEvent;
        public static event OnEndOfWhoisReceived OnEndOfWhoisReceivedEvent;
        public static event OnUsersOnChannelsReceived OnUsersOnChannelsReceivedEvent;
        public static event OnUserIsLoggedInAsReceived OnUserIsLoggedInAsReceivedEvent;
        public static event OnUserConnectingFromReceived OnUserConnectingFromReceivedEvent;
        public static event OnUserIsUsingModesReceived OnUserIsUsingModesReceivedEvent;
        public static event OnUnknownCommandReceived OnUnknownCommandReceivedEvent;
        public static event OnInviteCommandReceived OnInviteCommandReceivedEvent;
        public static event OnChannelUrlReceived OnChannelUrlReceivedEvent;

        // A list of defines for all server events.
        private const string WelcomeMessage = "001";
        private const string HostInformation = "002";
        private const string ServerCreated = "003";
        private const string IrcDaemon = "004";
        private const string CommandsSupported = "005";
        private const string UsersOnline = "251";
        private const string OperatorsOnline = "252";
        private const string UnknownConnectionsOnline = "253";
        private const string ChannelsFormed = "254";
        private const string ClientsAndServer = "255";
        private const string CurrentLocalUsers = "265";
        private const string CurrentGlobalUsers = "266";
        private const string UserIsARegisteredNick = "307";
        private const string UserIsAvailableForHelp = "310";
        private const string UserIsInfo = "311";
        private const string UserIsConnectedTo = "312";
        private const string UserIsAnOperator = "313";
        private const string UserIsIdle = "317";
        private const string EndOfWhoisList = "318";
        private const string UsersIsOnChannels = "319";
        private const string ChannelUrl = "328";
        private const string UserLoggedInAs = "330";
        private const string NoTopicSet = "331";
        private const string TopicSet = "332";
        private const string TopicSetBy = "333";
        private const string ChannelNames = "353";
        private const string EndOfNamelist = "366";
        private const string MotdMessage = "372";
        private const string MotdStart = "375";
        private const string MotdEnd = "376";
        private const string UserIsConnectingFrom = "378";
        private const string UserIsUsingModes = "379";
        private const string DisplayedHost = "396";
        private const string NoSuchNickOrChannel = "401";
        private const string NoSuchChannel = "403";
        private const string UnknownCommand = "421";
        private const string NameAlreadyInUse = "433";
        private const string NotOnChannel = "442";
        private const string ForwardingToChannel = "470";
        private const string CannotJoinChannel = "473";
        private const string InvalidChannelPassword = "475";
        private const string ModeEvent = "MODE";
        private const string NickEvent = "NICK";
        private const string JoinEvent = "JOIN";
        private const string PartEvent = "PART";
        private const string QuitEvent = "QUIT";
        private const string Pong = "PONG";
        private const string NoticeEvent = "NOTICE";
        private const string TopicEvent = "TOPIC";
        private const string KickEvent = "KICK";
        private const string PrivmsgEvent = "PRIVMSG";
        private const string ActionEvent = "ACTION";
        private const string PingEvent = "PING";
        private const string VersionCommand = "VERSION";
        private const string TimeCommand = "TIME";
        private const string PingCommand = "PING";
        private const string InviteCommand = "INVITE";

        public string Hostname { get; set; }
        public string InternalName { get; set; }
        public int Port { get; set; }
        public string UserName { get; set; }
        public string Nick { get; set; }
        public string OptionalName { get; set; }
        public string PersonalText { get; set; }

        private TcpClient IrcConnection = null;
        private NetworkStream NetworkStream = null;
        private StreamWriter DataWriter = null;
        private StreamReader DataReader = null;
        private Thread ServerThread = null;
        private Thread ConnectThread = null;
        public Logger Logger { get; private set; }

        // This holds the server index
        public int Index { get; private set; }
        // This holds all the text that was said on the server
        public string Text { get; set; }
        // Save the connection state
        public Core.eConnectionStates ConnectionState { get; set; }
        // Does the server support the NAMESX mode?
        public bool NamesXSupported { get; set; }
        // This bool holds whether or not the last user that was sent with /whois exists or not
        // this is being used so we don't send the end of the namelist command if the user doesn't exist
        public bool UserExists { get; set; }
        
        /// <summary>
        /// The constructor of the Server class
        /// </summary>
        /// <param name="serverHostname">The hostname to connect to</param>
        /// <param name="serverPort">The port to connect to</param>
        /// <param name="serverNick">The nickname to connect with</param>
        /// <param name="serverUser">The username to connect with</param>
        /// <param name="serverPersonalText">The personal text to connect with</param>
        /// <param name="Index">The server index</param>

        public Server(string hostname, int port, string nick, string user, string optionalName, string personalText)
        {
            int ServerCount = Core.ServerList.Count;
            Hostname = hostname;
            Port = port;
            UserName = user;
            Nick = nick;
            OptionalName = optionalName;
            PersonalText = personalText;
            Index = ServerCount;
            InternalName = "Server" + ServerCount;
            ConnectionState = 0;
            Text = "";

            Core.ServerList.Add(this);
            Core.LoggerList.Add(new Logger(hostname, hostname));

            Logger = Core.LoggerList[(Core.LoggerList.Count - 1)];
        }

        /// <summary>
        /// This function sends the specified data to the server.
        /// </summary>
        /// <param name="dataToSend">The data to be sent by the client.</param>

        public void SendData(string dataToSend)
        {
            // Check if we're connected to that server.
            if (ConnectionState >= Core.eConnectionStates.Connecting)
            {
                // We put this in a try catch struct to avoid errors.
                try
                {
                    // Send the data.
                    DataWriter.WriteLine(dataToSend);
                    DataWriter.Flush();
                }
                     
                // If an IOException occured, this means the user timed out.
                catch (IOException)
                {
                    OnClientTimeoutEvent(Index);
                }
            }
        }

        /// <summary>
        /// This method processes all server output in a background thread.
        /// </summary>
        /// <param name="Server">The reference to the Server object</param>

        private void ProcessServer()
        {
            // Create an infinite loop.
            while (true)
            {
                // And process any data coming from the server.
                ProcessOutput();

                // Let the thread sleep for 10 milliseconds.
                Thread.Sleep(10);
            }
        }

        /// <summary>
        /// This method connects to a server through a TCP connection in a background thread.
        /// </summary>
        /// <param name="Server">The reference to the Server object</param>
        /// <param name="Index">The Index of the server</param>

        private void AsyncConnect()
        {
            try
            {
                ConnectionState = Core.eConnectionStates.Connecting;

                // Create the connection.
                IrcConnection = new TcpClient(Hostname, Port);
                NetworkStream = IrcConnection.GetStream();

                // We create the data reader and writer.
                DataReader = new StreamReader(NetworkStream);
                DataWriter = new StreamWriter(NetworkStream);

                // Change the nick of the client.
                DataWriter.WriteLine("NICK " + Nick);
                DataWriter.WriteLine("USER " + UserName + " 0 * :" + PersonalText);
                DataWriter.Flush();

                DataWriter.WriteLine("PROTOCTL NAMESX");
                DataWriter.Flush();

                // Process any data coming from the server
                ProcessServer();
            }
            catch (SocketException Error)
            {
                // Call the event
                OnConnectionFailEvent(Index, Error.ErrorCode);
            }
        }

        /// <summary>
        /// This function connects the client to the server.
        /// </summary>

        public void Connect()
        {
            // Call the connectionstart event
            OnConnectionInitialisedEvent(Index);

            if(ConnectThread != null)
                ConnectThread.Abort();

            // Create the background thread
            ConnectThread = new Thread(() => AsyncConnect());
            ConnectThread.IsBackground = true;
            ConnectThread.Start();
        }

        /// <summary>
        /// This function checks whether the client is connected to the current server
        /// </summary>
        /// <returns>Returns false if there's no server connection, true otherwise.</returns>

        public bool IsConnected()
        {
            // If the connection state isn't CONNECTION_STATE_DISCONNECTED, return true
            if (ConnectionState == Core.eConnectionStates.Connected || ConnectionState == Core.eConnectionStates.Connecting)
                return true;

            // Otherwise, return false
            return false;
        }

        /// <summary>
        /// This method disconnects the client from the server
        /// </summary>
        /// <param name="disconnectReason">The reason to disconnect with</param>
        /// <param name="canReconnect">If CanRejoin is set to true, this object isn't destroyed</param>

        public void Disconnect(string disconnectReason = "", bool canReconnect = false)
        {
            // First we disconnect from the server.
            SendData("QUIT :" + disconnectReason);

            // Set the state
            ConnectionState = Core.eConnectionStates.Disconnected;

            if (ConnectThread != null) ConnectThread.Abort();
            if (ServerThread != null) ServerThread.Abort();
            if (IrcConnection != null) IrcConnection.Close();
            if (DataReader != null) DataReader.Close();
            if (DataWriter != null) DataWriter.Close();
            if (NetworkStream != null) NetworkStream.Close();

            Logger.Remove();

            if (!canReconnect)
            {
                // Destroy all channels and queries linked to this server object.
                int ChannelCount = Core.ChannelList.Count;
                int QueryCount = Core.QueryList.Count;

                for (int i = 0; i < ChannelCount; i++)
                {
                    if (Core.ChannelList[i] != null && Core.ChannelList[i].Server.Index == Index)
                        Core.ChannelList[i].Part("", false);
                }

                for (int i = 0; i < QueryCount; i++)
                {
                    if (Core.QueryList[i] != null && Core.QueryList[i].Server.Index == Index)
                        Core.QueryList[i].Remove();
                }

                Core.ServerList[this.Index] = null;
            }
        }

        /// <summary>
        /// This method concatenates all strings in a List of strings starting from the startIndex until the end of the list.
        /// This is used mainly for ease in the ProcessOutput() function.
        /// </summary>
        /// <param name="list">The list of strings</param>
        /// <param name="startIndex">The start index</param>
        /// <returns>The concatenated string, space seperated</returns>

        private string GetAllElementsFromIndexInList(List<string> list, int startIndex)
        {
            string Result = "";

            for(int i = startIndex; i < list.Count; i++)
            {
                Result += " " + list[i];
            }

            return Result.Remove(0, 1);
        }

        /// <summary>
        /// This function processes any data that the client gets from the server.
        /// </summary>


        public void ProcessOutput()
        {
            // Initalize the string, in case nothing was received.
            string InputLine = null;

            // Try reading the data.
            InputLine = DataReader.ReadLine();

            if (InputLine != null)
            {
                // And we process it to a proper output.
                string[] Split = InputLine.Split(new Char[] { ' ' });

                // Initialise all variables
                string NickName = "";
                string UserName = "";
                string HostName = "";

                // This list of strings will hold the input string split up with a space as delimiter
                List<string> Parameters = new List<string>();

                // Loop through the entire list
                for (int i = 0; i < Split.Count(); i++)
                {
                    // If it actually has a length
                    if (Split[i].Length > 0)
                        Parameters.Add((Split[i][0] == ':') ? Split[i].Remove(0, 1) : Split[i]);
                }

                if (Split.Count() > 0)
                {
                    // First get the user from the first part of the string.
                    Parameters[0] = (Parameters[0][0] == ':') ? Parameters[0].Remove(0, 1) : Parameters[0];

                    // First we split it up with ! as delimiter
                    string[] SplittedNickname = Parameters[0].Split('!');

                    if (SplittedNickname.Count() > 1)
                    {
                        string[] SplittedInfo = SplittedNickname[1].Split('@');

                        // Now we get the hostname and the username together with the nickname.
                        NickName = (SplittedNickname[0][0] == ':') ? SplittedNickname[0].Remove(0, 1) : SplittedNickname[0];
                        UserName = (SplittedInfo[0][0] == ':') ? SplittedInfo[0].Remove(0, 1) : SplittedInfo[0];
                        HostName = (SplittedInfo[1][0] == ':') ? SplittedInfo[1].Remove(0, 1) : SplittedInfo[1];
                    }
                    else
                    {
                        // There wasn't more than one split name, 
                        NickName = (SplittedNickname[0][0] == ':') ? SplittedNickname[0].Remove(0, 1) : SplittedNickname[0];
                    }
                }

                if (Parameters[0] == PingEvent)
                {
                    SendData("PONG " + Parameters[1]);
                }

                else if (Parameters[1] == PrivmsgEvent)
                {
                    if (Parameters[3] == ActionEvent)
                    {
                        if (Parameters[2] == Nick)
                        {
                            OnPrivateActionEvent(NickName, Index, GetAllElementsFromIndexInList(Parameters, 4));
                        }

                        else
                        {
                            OnActionEvent(Parameters[2], Index, NickName, GetAllElementsFromIndexInList(Parameters, 4));
                        }
                    }

                    else if (Parameters[3] == VersionCommand)
                    {
                        SendData("NOTICE " + Parameters[0].Substring(1, (Parameters[0].IndexOf('!') - 1)) + " :VERSION simpleIRC v" + Core.eApplicationVersion.Major + "." + Core.eApplicationVersion.Minor + " by Matthias Van Eeghem");
                    }

                    else if (Parameters[3] == TimeCommand)
                    {
                        SendData("NOTICE " + Parameters[0].Substring(1, (Parameters[0].IndexOf('!') - 1)) + " :TIME " + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToLongTimeString() + "");
                    }

                    else if (Parameters[3] == PingCommand)
                    {
                        long TimeNow = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;

                        SendData("NOTICE " + Parameters[0].Substring(1, (Parameters[0].IndexOf('!') - 1)) + " :PING " + TimeNow + "");
                    }

                    else
                    {
                        if (Parameters[2] == Nick)
                        {
                            OnPrivateMessageEvent(NickName, Index, GetAllElementsFromIndexInList(Parameters, 3));
                        }
                        else
                        {
                            OnMessageEvent(Parameters[2], Index, NickName, GetAllElementsFromIndexInList(Parameters, 3));
                        }
                    }
                }

                else if (Parameters[1] == WelcomeMessage || Parameters[1] == HostInformation || Parameters[1] == ServerCreated
                        || Parameters[1] == IrcDaemon || Parameters[1] == UsersOnline || Parameters[1] == OperatorsOnline
                        || Parameters[1] == UnknownConnectionsOnline || Parameters[1] == ChannelsFormed || Parameters[1] == ClientsAndServer
                        || Parameters[1] == CurrentLocalUsers || Parameters[1] == CurrentGlobalUsers || Parameters[1] == WelcomeMessage
                        || Parameters[1] == HostInformation || Parameters[1] == ServerCreated
                        || Parameters[1] == UsersOnline || Parameters[1] == ClientsAndServer
                        || Parameters[1] == CurrentLocalUsers || Parameters[1] == CurrentGlobalUsers)
                {
                    OnServerInformationReceivedEvent(Index, GetAllElementsFromIndexInList(Parameters, 3));
                }

                else if (Parameters[1] == CommandsSupported)
                {
                    if (GetAllElementsFromIndexInList(Parameters, 3).IndexOf("NAMESX") != -1)
                    {
                        NamesXSupported = true;
                    }

                    OnCommandsSupportedReceivedEvent(Index, GetAllElementsFromIndexInList(Parameters, 3));
                }

                else if (Parameters[1] == UserIsARegisteredNick)
                {
                    OnRegisteredNickReceivedEvent(Index, Parameters[3], GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == UserIsAvailableForHelp)
                {
                    OnIsAvailableForHelpReceivedEvent(Index, Parameters[3], GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == UserIsInfo)
                {
                    // Last user we checked with /whois exists
                    UserExists = true;

                    OnWhoisInfoReceivedEvent(Index, Parameters[3], Parameters[4], Parameters[5], Parameters[7]);
                }

                else if (Parameters[1] == UserIsConnectedTo)
                {
                    OnUserConnectedFromReceivedEvent(Index, Parameters[3], Parameters[4], GetAllElementsFromIndexInList(Parameters, 5));
                }

                else if (Parameters[1] == UserIsAnOperator)
                {
                    OnUserIsOperatorReceivedEvent(Index, Parameters[3], GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == UserIsIdle)
                {
                    OnUserIsIdlingReceivedEvent(Index, Parameters[3], Convert.ToInt32(Parameters[4]), Convert.ToInt32(Parameters[5]));
                }

                else if (Parameters[1] == EndOfWhoisList)
                {
                    if (UserExists)
                        OnEndOfWhoisReceivedEvent(Index, Parameters[3], GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == UsersIsOnChannels)
                {
                    OnUsersOnChannelsReceivedEvent(Index, Parameters[3], GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == ChannelUrl)
                {
                    OnChannelUrlReceivedEvent(Index, Parameters[3], GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == UserLoggedInAs)
                {
                    OnUserIsLoggedInAsReceivedEvent(Index, Parameters[3], Parameters[4], GetAllElementsFromIndexInList(Parameters, 5));
                }

                else if (Parameters[1] == UserIsConnectingFrom)
                {
                    OnUserConnectingFromReceivedEvent(Index, Parameters[3], GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == UserIsUsingModes)
                {
                    OnUserIsUsingModesReceivedEvent(Index, Parameters[3], GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == MotdStart)
                {
                    OnMotdStartEvent(Index, GetAllElementsFromIndexInList(Parameters, 3));
                }

                else if (Parameters[1] == MotdMessage)
                {
                    OnMotdReceiveEvent(Index, GetAllElementsFromIndexInList(Parameters, 3));
                }

                else if (Parameters[1] == NameAlreadyInUse)
                {
                    OnNameAlreadyInUseEvent(Index, Parameters[3], GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == MotdEnd)
                {
                    this.ConnectionState = Core.eConnectionStates.Connected;

                    OnMotdEndEvent(Index, GetAllElementsFromIndexInList(Parameters, 3));
                }

                else if (Parameters[1] == NoSuchNickOrChannel)
                {
                    OnNoSuchNickOrChannelEvent(Index, Parameters[3], GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == NoSuchChannel)
                {
                    Channel Channel = Core.GetChannelObject(Parameters[3], Index);
                    Channel.Part("", false);

                    OnNoSuchChannelEvent(Index, Parameters[3], GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == DisplayedHost)
                {
                    OnDisplayedHostReceivedEvent(Index, GetAllElementsFromIndexInList(Parameters, 3));
                }

                else if (Parameters[1] == InviteCommand)
                {
                    OnInviteCommandReceivedEvent(Index, NickName, Parameters[3]);
                }

                else if (Parameters[1] == JoinEvent)
                {
                    if (NickName != Nick)
                    {
                        Core.GetChannelObject(Parameters[2], Index).AddUser(NickName);

                        // Call the event
                        OnJoinEvent(Parameters[2], Index, NickName);
                    }
                }

                else if (Parameters[1] == PartEvent)
                {
                    if (NickName != Nick)
                    {
                        Channel Channel = Core.GetChannelObject(Parameters[2], Index);
                        string Reason = Parameters.Count > 3 ? GetAllElementsFromIndexInList(Parameters, 3) : "";

                        // Call the OnPart event.
                        OnPartEvent(Parameters[2], Index, NickName, Channel.GetUserHighestRights(NickName), Reason);

                        Channel.RemoveUser(NickName);
                    }
                }

                else if (Parameters[1] == CannotJoinChannel)
                {
                    OnCannotJoinEvent(Parameters[3], Index, GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == UnknownCommand)
                {
                    OnUnknownCommandReceivedEvent(Index, Parameters[3], GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == KickEvent)
                {
                    Channel Channel = Core.GetChannelObject(Parameters[2], Index);
                    string KickerName = (Parameters[0].Substring(0, (Parameters[0].IndexOf('!'))));
                    string Reason = Parameters.Count > 4 ? GetAllElementsFromIndexInList(Parameters, 4) : "";

                    OnKickEvent(Parameters[2], Index, KickerName, Parameters[3], Channel.GetUserHighestRights(Parameters[3]), Reason);

                    if (Channel.UserList.IndexOf(Parameters[3]) != -1)
                    {
                        Channel.RemoveUser(Parameters[3]);

                        if (Parameters[3] == this.Nick)
                            Channel.Part("", false);
                    }
                }

                else if (Parameters[1] == InvalidChannelPassword)
                {
                   Core.GetChannelObject(Parameters[3], Index).Password = "";
                    Channel Channel = Core.GetChannelObject(Parameters[3], Index);

                    // Remove the channel
                    if (Channel != null)
                        Channel.Part();

                    OnInvalidPasswordEvent(Parameters[3], Index, GetAllElementsFromIndexInList(Parameters, 4));
                }

                else if (Parameters[1] == TopicEvent)
                {
                    // Call the topic change event.
                    OnTopicChangeEvent(Parameters[2], Index, UserName, GetAllElementsFromIndexInList(Parameters, 3));
                }

                else if (Parameters[1] == ForwardingToChannel)
                {
                    // Join the new channel
                    new Channel(Index, Parameters[4], "").Join();

                    OnChannelForwardEvent(Parameters[3], Parameters[4], Index, GetAllElementsFromIndexInList(Parameters, 5));

                    // Part the old channel
                   Core.GetChannelObject(Parameters[3], Index).Part("", false);
                }

                else if (Parameters[1] == NoticeEvent)
                {
                    // If the receiver is the client's name, it's a private ntoice.
                    if (Parameters[2] == Nick)
                    {
                        // We need to remove the last character of each string
                        if (Parameters[3] == "PING")
                        {
                            OnPingReplyReceivedEvent(Index, NickName, Parameters[4].Remove((Parameters[4].Length - 1), 1));
                        }

                        else if (Parameters[3] == "VERSION")
                        {
                            OnVersionReplyReceivedEvent(Index, NickName, Parameters[4].Remove((Parameters[4].Length - 1), 1));
                        }

                        else if (Parameters[3] == "TIME")
                        {
                            OnTimeReplyReceivedEvent(Index, NickName, Parameters[4].Remove((Parameters[4].Length - 1), 1));
                        }
                        else
                        {
                            OnPrivateNoticeEvent(NickName, Index, GetAllElementsFromIndexInList(Parameters, 3));
                        }
                    }
                    // Otherwise, it was a notice sent to a channel.
                    else
                    {
                        OnChannelNoticeEvent(NickName, Parameters[2], Index, GetAllElementsFromIndexInList(Parameters, 3));
                    }
                }

                else if (Parameters[1] == QuitEvent)
                {
                    // Loop through all the channels
                    for (int TimesLooped = 0; TimesLooped < Core.ChannelList.Count; TimesLooped++)
                    {
                        // Do we have a valid channel?
                        if (Core.ChannelList[TimesLooped] != null)
                        {
                            Channel Channel = Core.ChannelList[TimesLooped];

                            // If the channel's server index isn't this index, ignore it. If the user isn't found on the userlist of that channel, ignore the channel.
                            if (Channel.Server.Index == Index && Channel.UserList.IndexOf(NickName) != -1)
                            {
                                string Reason = Parameters.Count > 3 ? GetAllElementsFromIndexInList(Parameters, 3) : "";
                                OnQuitEvent(Channel.Name, Index, NickName, UserName, HostName, Channel.GetUserHighestRights(NickName), Reason);

                                Channel.RemoveUser(NickName);
                            }
                        }
                    }
                }

                else if (Parameters[1] == TopicSetBy)
                {
                    Split = InputLine.Split(new Char[] { ' ' }, 6);
                    Channel Channel = Core.GetChannelObject(Parameters[3], Index);

                    if (Channel != null)
                    {
                        if (Channel.TopicSetBySent == false)
                        {
                            Channel.TopicSetBySent = true;

                            OnTopicSetByEvent(Parameters[3], Index, Parameters[4], Convert.ToInt32(Parameters[5]));
                        }
                    }
                }

                else if (Parameters[1] == ModeEvent)
                {
                    string Mode = GetAllElementsFromIndexInList(Parameters, 3);

                    // We need to send it to a channel
                    if(Parameters[2] != "")
                    {
                        Channel Channel = Core.GetChannelObject(Parameters[2], Index);

                        if (Channel != null)
                        {
                            if (this.NamesXSupported)
                            {
                                // Split the mode string (ie: "+ao Matthias Matthias|Laptop") into "+ao" and "Matthias Matthias|Laptop"
                                string[] ModeSplit = Mode.Split(new Char[] { ' ' }, 2);

                                // Make sure we actually have a name as second element of the ModeSplit array
                                if (ModeSplit.Length > 1)
                                {
                                    // Get the actual modes (ie: "+ao")
                                    string ModeList = ModeSplit[0];
                                    // Get the users (ie: "Matthias Matthias|Laptop")
                                    string Users = ModeSplit[1];

                                    // Split the user string in an array
                                    string[] UserList = Users.Split(new Char[] { ' ' });
                                    // This array will store the user's access before the mode change
                                    // This is used in the UI side of the OnModeChange event, this way
                                    // we can manually update each user in the UsersTreeView list without
                                    // reloading the entire nameslist.
                                    string[] RightsBeforeModeChange = new string[UserList.Length];

                                    // Loop through the entire list and get their access before the MODE change
                                    for (int i = 0; i < UserList.Length; i++)
                                    {
                                        RightsBeforeModeChange[i] = Channel.GetUserHighestRights(UserList[i]);
                                    }

                                    // Now we actually check which user needs to receive rights or have his rights removed
                                    // This list contains a list of booleans, true if the target user receives rights, false if the target user gets rights taken away.
                                    List<bool> GiveRights = new List<bool>();
                                    // This list contains a list of the actual rights as characters
                                    List<char> RightsList = new List<char>();

                                    // This boolean holds whether or not we're supposed to give or take any rights
                                    // For example, if +r was sent as mode, we're not supposed to give rights.
                                    bool ChangeRights = false;

                                    // Loop through the ModeList string
                                    for (int i = 0; i < ModeList.Length; i++)
                                    {
                                        // If the + character was found, the target user receives rights
                                        if (ModeList[i] == '+')
                                        {
                                            GiveRights.Add(true);
                                        }

                                        // If the - character was found, the target user gets rights taken away
                                        if (ModeList[i] == '-')
                                        {
                                            GiveRights.Add(false);
                                        }

                                        // If any other character was found, it's a character representing rights
                                        if (ModeList[i] == 'v' || ModeList[i] == 'h' || ModeList[i] == 'o' || ModeList[i] == 'a' || ModeList[i] == 'q')
                                        {
                                            // Set the bool to true
                                            ChangeRights = true;

                                            RightsList.Add(ModeList[i]);

                                            // If we received something like "+aooo", there will be 1 element in the GiveRights list,
                                            // but 4 elements in the RightsList list. We're adding the previous element from the RightsList
                                            // to the list so both lists will have equal elements.
                                            if (RightsList.Count > GiveRights.Count) GiveRights.Add(GiveRights[(GiveRights.Count - 1)]);
                                        }
                                    }

                                    if (ChangeRights)
                                    {
                                        // We now have the raw data we need to process the rights giving and taking
                                        // Loop through all the rights in the RightsList
                                        for (int i = 0; i < RightsList.Count; i++)
                                        {
                                            if (GiveRights[i])
                                            {
                                                Channel.GiveUserRights(UserList[i], RightsList[i]);
                                            }
                                            else
                                            {
                                                Channel.RemoveUserRights(UserList[i], RightsList[i]);
                                            }
                                        }
                                    }

                                    // Call the event, and pass on the list of users and their access that was changed
                                    OnModeChangeEvent(Parameters[2], Index, NickName, Mode, UserList, RightsBeforeModeChange);
                                }
                                else
                                {
                                    // No user's access was changed with this MODE command, call the event with empty user and access lists
                                    OnModeChangeEvent(Parameters[2], Index, NickName, Mode, null, null);
                                }
                            }

                            // NAMESX isn't supported
                            else
                            {
                                if (Channel != null)
                                    Channel.UserList = "";

                                // And simply send the NAMES command again
                                this.SendData("NAMES " + Parameters[2]);

                                // Call the event with empty user and access lists
                                OnModeChangeEvent(Parameters[2], Index, NickName, Mode, null, null);
                            }
                        }

                        else OnModeChangeEvent(Parameters[2], Index, NickName, Mode, null, null);
                    }

                    else OnModeChangeEvent(Parameters[2], Index, NickName, Mode, null, null);
                }

                else if (Parameters[1] == ChannelNames)
                {
                    string Names = GetAllElementsFromIndexInList(Parameters, 5);
                    // Get the list of all the names.
                    string[] NameList = Names.Split(new Char[] { ' ' });

                    Channel Channel = Core.GetChannelObject(Parameters[4], Index);

                    // If the channel exists and the channel list isn't equal to the one we already have, call the event
                    if (Channel != null && Channel.UserList != Names)
                    {
                        // Save the user list for that channel.
                        Channel.UserList = Channel.UserList + Names + " ";
                        Channel.UserList = Channel.UserList.Replace("  ", " ");

                        // And call the event
                        OnNamesReceivedEvent(Parameters[4], Index, NameList);
                    }
                }

                else if (Parameters[1] == TopicSet)
                {
                    Channel Channel = Core.GetChannelObject(Parameters[3], Index);

                    if (Channel != null)
                    {
                        // The topic hasn't been set yet.
                        if (Channel.Topic == null)
                        {
                            // Get the topic.
                            OnFoundTopicEvent(Parameters[3], Index, GetAllElementsFromIndexInList(Parameters, 4));

                            // Save the topic.
                            Channel.Topic = "Topic is '" + GetAllElementsFromIndexInList(Parameters, 4) + "'\r";
                        }
                    }
                }

                else if (Parameters[1] == NoTopicSet)
                {
                    string[] SplittedChannel = Parameters[3].Split(new Char[] { ' ' }, 4);
                    Channel Channel = Core.GetChannelObject(Parameters[3], Index);

                    if (Channel != null)
                    {
                        // The topic hasn't been set yet.
                        if (Channel.Topic == null)
                        {
                            OnEmptyTopicEvent(Parameters[3], Index);

                            // Save the channel topic.
                            Channel.Topic = "No topic set.\r";
                        }
                    }
                }

                else if (Parameters[1] == NickEvent)
                {
                    // Loop through all the channels
                    for (int TimesLooped = 0; TimesLooped < Core.ChannelList.Count; TimesLooped++)
                    {
                        if (Core.ChannelList[TimesLooped] != null)
                        {
                            if (Core.ChannelList[TimesLooped].Server != null)
                            {
                                if (Core.ChannelList[TimesLooped].Server.Index == Index)
                                {
                                    string UserLevel = Core.ChannelList[TimesLooped].GetUserHighestRights(NickName);
                                    Core.ChannelList[TimesLooped].UserList = Core.ChannelList[TimesLooped].UserList.Replace(" " + UserLevel + NickName + " ", " " + UserLevel + Parameters[2] + " ");

                                    // Call the topic change event.
                                    OnNickChangeEvent(Core.ChannelList[TimesLooped].Name, Index, NickName, Parameters[2]);
                                }
                            }
                        }
                    }
                }

                else if (Parameters[1] == Pong) { }
                else if (Parameters[1] == EndOfNamelist) { }
                else if (Parameters[1] == NotOnChannel) { }

                else
                {
                    OnMessageEvent("", Index, "", InputLine);
                }
            }
        }
    }
}
